//MountManager - the program for easy mounting of storage devices in Linux
//Copyright (C) 2007-2008 Tikhonov Sergey
//
//This file is part of MountManager Core
//
//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either version 2
//of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#include <QDBusInterface>
#include <QDBusReply>
#include <QDBusMetaType>
#include <QDBusArgument>
#include <math.h>
#include <QDebug>
#include <QFile>
#include <QTextStream>
#include "diskdevice.h"
#include "diskfstab.h"
#include "../gui/const.h"

Q_DECLARE_METATYPE(ChangeStruct)
Q_DECLARE_METATYPE(QList<ChangeStruct>)

const QDBusArgument &operator<<( QDBusArgument &arg, const ChangeStruct &change ) {
	arg.beginStructure();
	arg << change.propertyName << change.added << change.removed;
	arg.endStructure();
	return arg;
}

const QDBusArgument &operator>>( const QDBusArgument &arg, ChangeStruct &change ) {
	arg.beginStructure();
	arg >> change.propertyName >> change.added >> change.removed;
	arg.endStructure();
	return arg;
}

DiskDevice::DiskDevice(QDBusInterface *interface, const QString& udi) {

	fstabItem = 0;
	parentDevice = 0;
	cdOrFloppyChildDevice = 0;
	
	qDBusRegisterMetaType<ChangeStruct>();
	qDBusRegisterMetaType< QList<ChangeStruct> >();

	informationCenter = interface;
	deviceUdi = udi;

	QDBusReply<QString> categoryReply = informationCenter->call("GetPropertyString","info.category");
	deviceCategory = categoryReply.value();
	disk = categoryReply.value() == "volume" ? false : true;
	
	QDBusReply<QString> parentReply = informationCenter->call("GetPropertyString","info.parent");
	deviceParentUdi = parentReply.value();

	QDBusReply<QString> blockReply = informationCenter->call("GetPropertyString","block.device");
	deviceBlockName = blockReply.value();
	
	QDBusReply<QString> uuidReply = informationCenter->call("GetPropertyString","volume.uuid");
	deviceUuid = uuidReply.value();

	QDBusReply<QString> labelReply = informationCenter->call("GetPropertyString","volume.label");
	deviceLabel = labelReply.value();

	QDBusReply<QString> typeReply = informationCenter->call("GetPropertyString","storage.drive_type");
	deviceType = typeReply.value();
	cdOrFloppy = deviceType == "cdrom" || deviceType == "floppy";

	QDBusReply<QString> fileSystemReply = informationCenter->call("GetPropertyString","volume.fstype");
	deviceFileSystem = fileSystemReply.value();
	// Floppy disks always have fat file system
	if (deviceType == "floppy")
		deviceFileSystem = "vfat";

	QDBusReply<bool> removableReply = informationCenter->call("GetPropertyBoolean","storage.removable");
	removable = removableReply.isValid() ? removableReply.value() : false;

	QDBusReply<int> blocksReply = informationCenter->call("GetPropertyInteger","volume.num_blocks");
	deviceBlocks = blocksReply.isValid() ?  blocksReply.value() : 0;

	QDBusReply<QString> productReply = informationCenter->call("GetPropertyString","info.product");
	if (productReply.isValid()) deviceProduct = productReply.value();
	
	QDBusReply<QString> vendorReply = informationCenter->call("GetPropertyString","info.vendor");
	if (vendorReply.isValid()) deviceVendor = vendorReply.value();
	
	QDBusReply<QString> storageBusReply = informationCenter->call("GetPropertyString","storage.bus");
	if (storageBusReply.isValid()) deviceBus = storageBusReply.value();
	usb = deviceBus == "usb";
	
	QDBusReply<QString> modelReply = informationCenter->call("GetPropertyString","storage.model");
	if (modelReply.isValid()) deviceModel = modelReply.value();
	
	QDBusReply<bool> supportDvdReply = informationCenter->call("GetPropertyBoolean","storage.cdrom.dvd");
	supportDvd = supportDvdReply.isValid() ? supportDvdReply.value() : false;
	
	QDBusReply<qulonglong> sizeReply;
	sizeReply = isDisk() ? informationCenter->call("GetProperty","storage.size") : informationCenter->call("GetProperty","volume.size");
	deviceSize = sizeReply.isValid() ? sizeReply.value() : 0;

	QDBusReply<int> majorReply = informationCenter->call("GetPropertyInteger","block.major");
	major = majorReply.isValid() ? majorReply.value() : 0;
	
	QDBusReply<int> minorReply = informationCenter->call("GetPropertyInteger","block.minor");
	minor = minorReply.isValid() ? minorReply.value() : 0;
	
	
	informationCenter->connection().connect(HAL_SERVICE,deviceUdi,HAL_DEVICE_INTERFACE,"PropertyModified",
						   	this,SLOT(propertyModified(int, const QList<ChangeStruct>&)));
}

DiskDevice::~DiskDevice() {
	delete informationCenter;
}

const QString& DiskDevice::blockName() const {
	return deviceBlockName;
}

const QString& DiskDevice::udi() const {
	return deviceUdi;
}

const QString& DiskDevice::parentUdi() const {
	return deviceParentUdi;
}

const QString& DiskDevice::uuid() const {
	if (cdOrFloppyChildDevice)
		return cdOrFloppyChildDevice->uuid();
	return deviceUuid;
}

const QString& DiskDevice::label() const {
	return deviceLabel;
}

const QString& DiskDevice::fileSystem() const {
	if (cdOrFloppyChildDevice)
		return cdOrFloppyChildDevice->fileSystem();
	return deviceFileSystem;
}

const QString DiskDevice::fstabFileSystem() const {
	return deviceFstabFileSystem;
}

const QString& DiskDevice::type() const {
	return deviceType;
}

const QString& DiskDevice::category() const {
	return deviceCategory;
}

const QString& DiskDevice::fstabOptions() const {
	return deviceFstabOptions;
}

const QString& DiskDevice::currentMountPoint() {
	if (cdOrFloppyChildDevice)
		return cdOrFloppyChildDevice->currentMountPoint();
	QDBusReply<QString> mountPointReply = informationCenter->call("GetPropertyString","volume.mount_point");
	deviceCurrentMountPoint = mountPointReply.value();
	return deviceCurrentMountPoint;
}

const QString& DiskDevice::fstabMountPoint() const {
	return deviceFstabMountPoint;
}

QString DiskDevice::size(int format) {
	if (cdOrFloppyChildDevice)
		return cdOrFloppyChildDevice->size(format);
	switch (format) {
		case Bytes:
			return QString::number(deviceSize) + " Bytes";
		case KiloBytes:
			return QString::number(round(deviceSize/1024.0*10)/10.0) + "Kb";
		case MegaBytes:
			return QString::number(round(deviceSize/1048576.0*10)/10.0) + "Mb";
		case GigaBytes:
			return QString::number(round(deviceSize/1073741824.0*10)/10.0) + "Gb";
		case Truncation:
			if (deviceSize/1073741824 > 0)
				return size(GigaBytes);
			else if (deviceSize/1048576 > 0)
				return size(MegaBytes);
			else if (deviceSize/1024 > 0)
				return size(KiloBytes);
			return size(Bytes);
			break;
	}
	return QString();
}

const QString& DiskDevice::product() const {
	return deviceProduct;
}

const QString& DiskDevice::model() const {
	return deviceModel;
}

const QString& DiskDevice::bus() const {
	return deviceBus;
}

const QString& DiskDevice::vendor() const {
	return deviceVendor;
}		

qulonglong DiskDevice::size() const {
	return deviceSize;
}

int DiskDevice::blocks() const {
	return deviceBlocks;
}
		
int DiskDevice::majorNumber() const {
	return major;
}

int DiskDevice::minorNumber() const {
	return minor;
}

int DiskDevice::fsck() const {
	return fstabItem ? fstabItem->itemFsck : 0;
}
		
DiskDevice *DiskDevice::parent() {
	return parentDevice;
}

bool DiskDevice::isMounted() const {
	if (cdOrFloppyChildDevice)
		return cdOrFloppyChildDevice->isMounted();
	// To define mounting status of swap we have to parse file /proc/swaps
	if (deviceFileSystem == "swap") {
		QFile swapsFile("/proc/swaps");
		swapsFile.open(QIODevice::ReadOnly);
		QTextStream swapsStream(&swapsFile);
		QString shortBlockName = deviceBlockName;
		shortBlockName.remove("/dev/");
		if (swapsStream.readAll().contains(shortBlockName))
			return true;
		return false;
	}
	QDBusReply<bool> mountedReply = informationCenter->call("GetPropertyBoolean","volume.is_mounted");
	return mountedReply.isValid() ? mountedReply.value() : false;
}

bool DiskDevice::isRemovable() const {
	return removable;
}

bool DiskDevice::isDisk() const {
	return disk;
}

bool DiskDevice::isCdOrFloppy() const {
	return cdOrFloppy;
}

bool DiskDevice::isSupportDvd() const {
	return supportDvd;
}

bool DiskDevice::isUsb() const {
	return usb;
}

bool DiskDevice::isCdOrFloppyInserted() const {
	return cdOrFloppyChildDevice != 0;
}

bool DiskDevice::dump() const {
	return fstabItem ? fstabItem->itemDump : false;
}
		
//===================== SLOTS =================================//

void DiskDevice::propertyModified(int, const QList<ChangeStruct> &changes) {
	foreach (const ChangeStruct changeStruct, changes) {
		qDebug() << "[I] Device" << deviceUdi << "changed property:" << changeStruct.propertyName;
		if (changeStruct.propertyName == "volume.mount_point")
			emit (currentMountPointChanged(currentMountPoint()));
		else if (changeStruct.propertyName == "volume.is_mounted")
			emit (mountedStatusChanged(isMounted()));
	}
}


//===================== Functions of some variables settings =========================//

void DiskDevice::setFstabItem(DiskFstabItem *item) {
	fstabItem = item;
	if (fstabItem == 0x0) fstabItem = 0;
	if (fstabItem != 0) {
		deviceFstabMountPoint = fstabItem->itemMountPoint;
		deviceFstabOptions = fstabItem->itemOptions;
		deviceFstabFileSystem = fstabItem->itemFileSystem;
	}
}

void DiskDevice::setParent(DiskDevice *parent) {
	parentDevice = parent;
	if (!removable && parentDevice)
		removable = parentDevice->isRemovable();
	if (deviceType.isEmpty())
		deviceType = parent->type();
	usb = parent->isUsb();
	cdOrFloppy = deviceType == "cdrom" || deviceType == "floppy";
	deviceBus = parent->bus();
	deviceModel = parent->model();
}

void DiskDevice::setCdOrFloppyChild(DiskDevice *child) {
	cdOrFloppyChildDevice = child;
	connect(cdOrFloppyChildDevice,SIGNAL(currentMountPointChanged(const QString&)),this,SIGNAL(currentMountPointChanged(const QString&)));
	connect(cdOrFloppyChildDevice,SIGNAL(mountedStatusChanged(bool)),this,SIGNAL(mountedStatusChanged(bool)));
}

void DiskDevice::removeCdOrFloppyChild() {
	cdOrFloppyChildDevice = 0;
}
